package com.gmcloud.common.security.config;

import cn.hutool.extra.spring.SpringUtil;
import com.gmcloud.common.core.constant.SecurityConstants;
import com.gmcloud.common.security.entity.GmUser;
import com.gmcloud.common.security.service.GmUserDetailsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;

import java.security.Principal;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * @author zl
 * @since 2022/5/28
 * token拦截器
 */

public class GmCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {


    private static final Logger LOGGER = LoggerFactory.getLogger(GmCustomOpaqueTokenIntrospector.class);
    private final OAuth2AuthorizationService authorizationService;

    public GmCustomOpaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) {
        this.authorizationService = authorizationService;
    }

    @Override
    public OAuth2AuthenticatedPrincipal introspect(String token) {
        OAuth2Authorization oldAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
        if (Objects.isNull(oldAuthorization)) {
            throw new InvalidBearerTokenException(token);
        }

        // 客户端模式默认返回
        if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(oldAuthorization.getAuthorizationGrantType())) {
            return new GmClientCredentialsOAuth2AuthenticatedPrincipal(oldAuthorization.getAttributes(),
                    AuthorityUtils.NO_AUTHORITIES, oldAuthorization.getPrincipalName());
        }

        Map<String, GmUserDetailsService> userDetailsServiceMap = SpringUtil
                .getBeansOfType(GmUserDetailsService.class);

        Optional<GmUserDetailsService> optional = userDetailsServiceMap.values().stream()
                .filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(),
                        oldAuthorization.getAuthorizationGrantType().getValue()))
                .max(Comparator.comparingInt(Ordered::getOrder));

        UserDetails userDetails = null;
        try {
            Object principal = Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName());
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal;
            Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal();
            if (optional.isPresent()) {
                userDetails = optional.get().loadUserByUser((GmUser) tokenPrincipal);
            }
        } catch (UsernameNotFoundException notFoundException) {
            throw notFoundException;
        } catch (Exception ex) {
            LOGGER.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage());
        }

        // 注入扩展属性,方便上下文获取客户端ID
        GmUser user = (GmUser) userDetails;
        Objects.requireNonNull(user)
                .getAttributes()
                .put(SecurityConstants.CLIENT_ID, oldAuthorization.getRegisteredClientId());
        return user;
    }

}
